#include "V8ScriptPlugin.h"

// Extracts a C string from a V8 Utf8Value.
// swiped from the V8 sample shell.cc
const char* ToCString(const v8::String::Utf8Value& value)
{
  return *value ? *value : "<string conversion failed>";
}

V8ScriptPlugin::V8ScriptPlugin(void)
{
	HandleScope handle_scope;
    Handle<ObjectTemplate> global = ObjectTemplate::New();
    
    global->Set(v8::String::New("addLight"), v8::FunctionTemplate::New(AddLight));
    global->Set(v8::String::New("addEntity"), v8::FunctionTemplate::New(AddEntity));

    context = Context::New(0, global);
    context->Enter();
}

V8ScriptPlugin::~V8ScriptPlugin(void)
{
    context->Exit();
    context.Dispose();
    context.Clear();
    v8::V8::Dispose();
}

const String& V8ScriptPlugin::getName() const
{
    return pluginName;
}

void V8ScriptPlugin::install()
{
    ScriptManager::getSingleton().Register(this);
}

void V8ScriptPlugin::initialise()
{
}

void V8ScriptPlugin::shutdown()
{
}

void V8ScriptPlugin::uninstall()
{
}

void V8ScriptPlugin::Setup(SceneManager* sceneManager, Camera* camera, Viewport* viewport)
{
    this->sceneManager = sceneManager;
    this->camera = camera;
    this->viewport = viewport;
}

// swiped and modified from the V8 sample shell.cc
bool V8ScriptPlugin::Execute(std::string scriptCode)
{
    HandleScope handle_scope;
    TryCatch try_catch;

    result.clear();
    error.clear();

    Handle<Script> script = Script::Compile(v8::String::New(scriptCode.data()), v8::String::New("(v8 script plugin)"));

    if (script.IsEmpty()) {
        // Print errors that happened during compilation.
        BuildErrorString(try_catch);
        return false;
    } else {
        v8::Handle<v8::Value> retval = script->Run();
        if (retval.IsEmpty()) {
            // Print errors that happened during execution.
            BuildErrorString(try_catch);
            return false;
        } else {
            if (!retval->IsUndefined()) {
                // If all went well and the result wasn't undefined then print
                // the returned value.
                v8::String::Utf8Value str(retval);
                const char* cstr = ToCString(str);
                result.append(cstr);
            }
        }
    }
    
    return true;
}

String V8ScriptPlugin::Result() { return result; }
String V8ScriptPlugin::Error() { return error; }

// swiped and modified from the V8 sample shell.cc
void V8ScriptPlugin::BuildErrorString(TryCatch& try_catch)
{
    StringStream os;
    HandleScope handle_scope;
    v8::String::Utf8Value exception(try_catch.Exception());
    const char* exception_string = ToCString(exception);
    Handle<v8::Message> message = try_catch.Message();
    if (message.IsEmpty()) {
        // V8 didn't provide any extra information about this error; just
        // print the exception.
        os << exception_string;
    } else {
        // Print (filename):(line number): (message).
        v8::String::Utf8Value filename(message->GetScriptResourceName());
        const char* filename_string = ToCString(filename);
        int linenum = message->GetLineNumber();
        os << filename_string << ":" << linenum << ": " << exception_string << "\n";
        // Print line of source code.
        v8::String::Utf8Value sourceline(message->GetSourceLine());
        const char* sourceline_string = ToCString(sourceline);
        os << sourceline_string << "\n";
        // Print wavy underline (GetUnderline is deprecated).
        int start = message->GetStartColumn();
        for (int i = 0; i < start; i++) {
            os << " ";
        }
        int end = message->GetEndColumn();
        for (int i = start; i < end; i++) {
            os << "^";
        }
        os << "\n";
        v8::String::Utf8Value stack_trace(try_catch.StackTrace());
        if (stack_trace.length() > 0) {
            const char* stack_trace_string = ToCString(stack_trace);
            os << stack_trace_string << "\n";
        }
    }

    error = os.str();
}